package xyz.scootaloo.mono.base.util

import cn.hutool.core.util.StrUtil
import java.util.*
import javax.servlet.http.HttpServletRequest

/**
 * Ip 工具
 *
 * @author flutterdash@qq.com
 * @since 2021/7/29 14:14
 */
object IpUtil {

    /**
     * 通过请求报文 [HttpServletRequest] 对象, 拿到其中指向的IP地址
     *
     * 通过http属性中的 [remoteMark], 和 [HttpServletRequest.getRemoteAddr] 获取IP地址
     *
     * 不保证一定有效
     *
     * @param request 请求报文
     */
    fun getRealIpAddress(request: HttpServletRequest): String {
        val mark: String? = tryGetRemoteMarkOrNull(request)
        if (mark != null) {
            val tokenizer = StringTokenizer(mark, ",")
            while (tokenizer.hasMoreTokens()) {
                val ipHolder = IpAddressHolder(tokenizer.nextToken())
                if (ipHolder.isValidIPv4() && ipHolder.isPrivateIPv4()) {
                    return ipHolder.ipAddress
                }
            }
        }
        return getRemoteAddressOrUnknown(request)
    }

    private fun tryGetRemoteMarkOrNull(request: HttpServletRequest) = try {
        request.getHeader(remoteMark)
    } catch (ignore: Throwable) {
        null
    }

    private fun getRemoteAddressOrUnknown(request: HttpServletRequest) = try {
        request.remoteAddr
    } catch (ignore: Throwable) {
        "unknown"
    }

    private class IpAddressHolder(address: String) {
        val ipAddress: String = address.trim()
        private var longIp = 0
        private var offsetPos = 0

        fun isValidIPv4(): Boolean {
            val parts = StrUtil.split(ipAddress, '.')
            if (parts.size != 4)
                return false
            for (part in parts) {
                if (!isBetween0And255(part))
                    return false
            }
            return true
        }

        fun isPrivateIPv4(): Boolean {
            /**
             * 10.0.0.0 ~ 10.255.255.255
             * 172.16.0.0 ~ 172.31.255.255
             * 192.168.0.0 ~ 192.168.255.255
             */
            return (longIp in 167772160..184549375) ||
                    (longIp in 2886729728..2887778303) ||
                    (longIp in 3232235520..3232301055)
        }

        private fun isBetween0And255(numStr: String): Boolean {
            return try {
                val number = Integer.parseInt(numStr)
                addIpSegment(number)
                number in 0..255
            } catch (e: Exception) {
                false
            }
        }

        private fun addIpSegment(segment: Int) {
            longIp += segment.shl(ipOffset[offsetPos])
            offsetPos++
        }
    }

    private const val remoteMark = "x-forwarded-for"
    private val ipOffset = intArrayOf(24, 16, 8, 0)
}
